package org.dandelion.papercheck.service.impl;


import org.dandelion.papercheck.dao.ArticleDao;
import org.dandelion.papercheck.entity.Article;
import org.dandelion.papercheck.service.DupChkService;
import org.dandelion.papercheck.service.WeightService;
import org.dandelion.papercheck.util.Hash;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


// 采用lowbit 优化SimHash速度
@Service
public class DupChkServiceImp implements DupChkService {
    /**
     * SimHah算法查重实现类
     */

    private final Hash hash;
    private final ArticleDao articleDao;
    private final WeightService weightService;

    @Autowired
    public DupChkServiceImp(Hash hash, ArticleDao articleDao, WeightService weightService) {
        this.hash = hash;
        this.articleDao = articleDao;
        this.weightService = weightService;
    }

    /**
     * SimHah算法指纹提取
     */
    @Override
    public String extractFingerprint(Map<String, Integer> weightMap) {
        // 拿到hash后的结果的长度，按照长度声明结果数组
        int length = hash.wordHash("你好").length;
        List<Integer> result = new ArrayList<>();

        for (int i = 0; i < length; i++) {
            result.add(0);
        }

        int temp = 0;
        // 对每一分词结果进行hash后加权
        // 将加权后的结果求和

        for (Map.Entry<String, Integer> entry : weightMap.entrySet()) {
            int[] hashResult = hash.wordHash(entry.getKey());

            for (int i = 0; i < hashResult.length; i++) {
                if (hashResult[i] == 0) {
                    temp = -1 * entry.getValue();
                } else {
                    temp = entry.getValue();
                }
                result.set(i, result.get(i) + temp);
            }
        }

        for (int i = 0; i < length; i++) {
            if (result.get(i) >= 0) {
                result.set(i, 1);
            } else {
                result.set(i, 0);
            }
        }
        StringBuilder ans = new StringBuilder();
        for (int num : result) {
            ans.append(num);
        }
        return ans.toString();
    }

    @Override
    public Map<String, Double> singleChk(String fingerprint) {
        Map<String, Double> res = new HashMap<>();
        BigInteger thisFingerPrint = new BigInteger(fingerprint, 2);
        List<Article> all = articleDao.findAll();
        BigInteger zero = new BigInteger("0");
        BigInteger result = null;
        int count = 0;
        BigInteger artFingerPrint = null;

        for (Article article : all) {
            String tempFinger = article.getFingerprint();
            if (tempFinger == null || "".equals(tempFinger)) {
                continue;
            }

            count = 0;
            artFingerPrint = new BigInteger(tempFinger, 2);
            result = thisFingerPrint.xor(artFingerPrint);
            while (!result.equals(zero)) {
                count += 1;
                result = result.subtract(result.and(result.negate()));
            }
            res.put(article.getFileName(), (double) count);
        }

        return res;
    }

    @Override
    public List<List<Double>> multiChk(List<String> fingerPrints) {
        BigInteger zero = new BigInteger("0");
        List<List<Double>> ans = new ArrayList<>();
        for (int i = 0; i < fingerPrints.size(); i++) {
            ans.add(new ArrayList<>());
            for (int j = 0; j < fingerPrints.size(); j++) {
                if (i > j) {
                    ans.get(i).add(ans.get(j).get(i));
                } else if (i == j) {
                    ans.get(i).add(0.0);
                } else {
                    int count = 0;
                    BigInteger fingerPrintI = new BigInteger(fingerPrints.get(i));
                    BigInteger fingerPrintJ = new BigInteger(fingerPrints.get(j));
                    BigInteger result = fingerPrintI.xor(fingerPrintJ);
                    while (!result.equals(zero)) {
                        count += 1;
                        result = result.subtract(result.and(result.negate()));
                    }
                    ans.get(i).add((double) count);
                }
            }
        }
        return ans;
    }
}
